package option

import (
	"bytes"
	"gitee.com/lipore/plume/codegen/code"
	"gitee.com/lipore/plume/codegen/utils"
	"text/template"

	"golang.org/x/tools/imports"
)

type Generator struct {
	FilePath         string
	OptionStructName string
	stru             *code.Struct
}

func (g *Generator) parseFile() {
	f, err := code.ExtractComponentsByPath(g.FilePath, code.WithStructs())
	if err != nil {
		panic(err)
	}
	if stru, exist := f.Structs.ByName(g.OptionStructName); exist {
		g.stru = stru
	}
}

func (g *Generator) Generate() []byte {

	buffer := new(bytes.Buffer)
	g.parseFile()
	if g.stru.Name == "" {
		panic("can't parse option struct")
	}

	headerTemplate := `//This file is generated by optiongen don't modify
package {{.PackageName}}

type Option interface {
	Apply(*{{.OptionStructName}})
}

type fnOption struct{
	fn func(*{{.OptionStructName}})
}

func (fo *fnOption) Apply(o *{{.OptionStructName}}) {
	fo.fn(o)
}
`
	templ, err := template.New("objectMapper").Parse(headerTemplate)
	if err != nil {
		panic(err)
	}
	err = templ.Execute(buffer, map[string]string{"PackageName": g.stru.PackageAlias, "OptionStructName": g.stru.Name})
	if err != nil {
		panic(err)
	}
	optionTemplate := `
func With{{PascalCase .Name}}({{CamelCase .Name}} {{.Type}}) *fnOption{
	return &fnOption{
		fn: func(o *{{.OptionStructName}}) {
			o.{{.Name}} = {{CamelCase .Name}}
		},
	}
}
`
	arrayOptionTemplate := `
func With{{PascalCase .Name}}({{CamelCase .Name}} ...{{.Type}}) *fnOption{
	return &fnOption{
		fn: func(o *{{.OptionStructName}}) {
			o.{{.Name}} = append(o.{{.Name}}, {{CamelCase .Name}}...)
		},
	}
}
`

	templ, err = template.New("withOpt").Funcs(map[string]any{"PascalCase": utils.PascalCase, "CamelCase": utils.CamelCase}).Parse(optionTemplate)
	if err != nil {
		panic(err)
	}
	arrayTempl, err := template.New("withOptItem").Funcs(map[string]any{"PascalCase": utils.PascalCase, "CamelCase": utils.CamelCase}).Parse(arrayOptionTemplate)
	for _, field := range g.stru.Fields {
		if arrayType, ok := field.Type.(code.ArrayType); ok {
			err = arrayTempl.Execute(buffer, map[string]string{"Name": field.Name, "Type": arrayType.ContainedType.Code(), "OptionStructName": g.stru.Name})
		} else {
			err = templ.Execute(buffer, map[string]string{"Name": field.Name, "Type": field.Type.Code(), "OptionStructName": g.stru.Name})
		}
		if err != nil {
			panic(err)
		}
	}
	codes, err := imports.Process("", buffer.Bytes(), nil)
	if err != nil {
		panic(err)
	}
	return codes
}
